﻿//    TestGenerator - experiment with piping data into ffmpeg in "pipefmt"
//    Copyright (C) 2012  Juergen Bohl

//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.

//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.

//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace TestGenerator
{
    #region using directives
    using System;
    using System.Runtime.InteropServices;
    using System.Runtime.ConstrainedExecution;
    #endregion

    /// <summary> Convert a BGR24 image to YV12. If IntelPerformancePrimitives are present (version 7), then
    /// 		  the IPP-function "ippiBGRToYCbCr420_8u_C3P3R" is used. Otherwise we provide a fallback. 
    /// </summary>
    internal static class BGRtoYV12
    {
        private static ippiBGRToYCbCr420_8u_C3P3RDelegate delegateIppippiBGRToYCbCr420_8u_C3P3R;

        private static convertRgb24ToYv12 delegateRgb24toYv12;

        public static unsafe void ConvertRgb24ToYv12(
                                IntPtr bitmapRgb24,
                                int strideSource,
                                int width,
                                int height,
                                IntPtr destYv12Y,
                                int strideYv12Y,
                                IntPtr destYv12U,
                                IntPtr destYv12V,
                                int strideYv12Uv)
        {
            if (delegateRgb24toYv12 == null)
            {
                delegateIppippiBGRToYCbCr420_8u_C3P3R = TryToLoadIpp();
                if (delegateIppippiBGRToYCbCr420_8u_C3P3R == null)
                {
                    delegateRgb24toYv12 = BGRtoYv12Fallback;
                }
                else
                {
                    delegateRgb24toYv12 = BGRtoYv12Ipp;
                }
            }

            delegateRgb24toYv12(bitmapRgb24, strideSource, width, height, destYv12Y, strideYv12Y, destYv12U, destYv12V, strideYv12Uv);
        }

        private static void BGRtoYv12Fallback(IntPtr bitmapRgb24, int strideSource, int width, int height, IntPtr destYv12Y, int strideYv12Y, IntPtr destYv12U, IntPtr destYv12V, int strideYv12Uv)
        {
            // quick'n'dirty hack for converting rgb24 to yv12
            unsafe
            {
                for (int y = 0; y < height; y++)
                {
                    byte* yline = (byte*)(destYv12Y + y * strideYv12Y).ToPointer();
                    byte* uline = (byte*)(destYv12U + y / 2 * strideYv12Uv).ToPointer();
                    byte* vline = (byte*)(destYv12V + y / 2 * strideYv12Uv).ToPointer();
                    byte* rgbIndex = (byte*)(bitmapRgb24 + y * strideSource).ToPointer();

                    for (int x = 0; x < width; x += 2)
                    {
                        *yline = (byte)(((int)(30 * rgbIndex[2]) + (int)(59 * rgbIndex[1]) + (int)(11 * rgbIndex[0])) / 100);
                        if (y % 2 == 0)
                        {
                            int u1 = (byte)(((int)(-17 * 30 * rgbIndex[2]) - (int)(33 * rgbIndex[1]) + (int)(50 * rgbIndex[0]) + 12800) / 100);
                            int v1 = (byte)(((int)(50 * rgbIndex[2]) - (int)(42 * rgbIndex[1]) - (int)(8 * rgbIndex[0]) + 12800) / 100);
                            int u2 = (byte)(((int)(-17 * 30 * rgbIndex[2 + 3]) - (int)(33 * rgbIndex[1 + 3]) + (int)(50 * rgbIndex[0 + 3]) + 12800) / 100);
                            int v2 = (byte)(((int)(50 * rgbIndex[2 + 3]) - (int)(42 * rgbIndex[1 + 3]) - (int)(8 * rgbIndex[0 + 3]) + 12800) / 100);

                            int u3 = (byte)(((int)(-17 * 30 * (rgbIndex + strideSource)[2]) - (int)(33 * (rgbIndex + strideSource)[1]) + (int)(50 * (rgbIndex + strideSource)[0]) + 12800) / 100);
                            int v3 = (byte)(((int)(50 * (rgbIndex + strideSource)[2]) - (int)(42 * (rgbIndex + strideSource)[1]) - (int)(8 * (rgbIndex + strideSource)[0]) + 12800) / 100);
                            int u4 = (byte)(((int)(-17 * 30 * (rgbIndex + strideSource)[2 + 3]) - (int)(33 * (rgbIndex + strideSource)[1 + 3]) + (int)(50 * (rgbIndex + strideSource)[0 + 3]) + 12800) / 100);
                            int v4 = (byte)(((int)(50 * (rgbIndex + strideSource)[2 + 3]) - (int)(42 * (rgbIndex + strideSource)[1 + 3]) - (int)(8 * (rgbIndex + strideSource)[0 + 3]) + 12800) / 100);
                            *uline = (byte)((u1 + u2 + u3 + u4) / 2);
                            *vline = (byte)((v1 + v2 + v3 + v4) / 2);
                            uline++;
                            vline++;
                        }

                        rgbIndex += 3;
                        yline++;
                        *yline = (byte)(((int)(30 * rgbIndex[2]) + (int)(59 * rgbIndex[1]) + (int)(11 * rgbIndex[0])) / 100);
                        rgbIndex += 3;
                        yline++;
                    }
                }
            }
        }

        private static void BGRtoYv12Ipp(IntPtr bitmapRgb24, int strideSource, int width, int height, IntPtr destYv12Y, int strideYv12Y, IntPtr destYv12U, IntPtr destYv12V, int strideYv12Uv)
        {
            unsafe
            {
                byte** destinationYv12Pointers = stackalloc byte*[3];
                destinationYv12Pointers[0] = (byte*)destYv12Y.ToPointer();
                destinationYv12Pointers[1] = (byte*)destYv12U.ToPointer();
                destinationYv12Pointers[2] = (byte*)destYv12V.ToPointer();
                int* pitches = stackalloc int[3];
                pitches[0] = strideYv12Y;
                pitches[1] = strideYv12Uv;
                pitches[2] = strideYv12Uv;
                delegateIppippiBGRToYCbCr420_8u_C3P3R(
                                                (byte*)bitmapRgb24.ToPointer(),
                                                strideSource,
                                                destinationYv12Pointers,
                                                pitches,
                                                new IppiSize(width, height));
            }
        }

        private struct IppiSize
        {
            public int width;
            public int height;
            public IppiSize(int width, int height)
            {
                this.width = width;
                this.height = height;
            }
        };

        private unsafe delegate int ippiBGRToYCbCr420_8u_C3P3RDelegate(byte* pSrc, int srcStep, byte** pDst, int* dstStep, IppiSize roiSize);

        private delegate void convertRgb24ToYv12(IntPtr bitmapRgb24, int strideSource, int width, int height, IntPtr destYv12Y, int strideYv12Y, IntPtr destYv12U, IntPtr destYv12V, int strideYv12Uv);

        [System.Flags]
        enum LoadLibraryFlags : uint
        {
            DONT_RESOLVE_DLL_REFERENCES = 0x00000001,
            LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,
            LOAD_LIBRARY_AS_DATAFILE = 0x00000002,
            LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,
            LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020,
            LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008,
            LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100
        }

        [DllImport("kernel32.dll")]
        static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);

        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [DllImport("kernel32", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool FreeLibrary(IntPtr hModule);


        private unsafe static ippiBGRToYCbCr420_8u_C3P3RDelegate TryToLoadIpp()
        {
            const string dllName = "ippcc-7.0.dll";
            IntPtr dllHandle = LoadLibraryEx(dllName, IntPtr.Zero, 0/*LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR*/);
            if (dllHandle == IntPtr.Zero)
            {
                return null;
            }

            IntPtr p = GetProcAddress(dllHandle, "ippiBGRToYCbCr420_8u_C3P3R");
            if (p == IntPtr.Zero)
            {
                FreeLibrary(p);
                return null;
            }

            var ippFunc = Marshal.GetDelegateForFunctionPointer(p, typeof(ippiBGRToYCbCr420_8u_C3P3RDelegate)) as ippiBGRToYCbCr420_8u_C3P3RDelegate;
            if (ippFunc == null)
            {
                FreeLibrary(p);
                return null;
            }

            return ippFunc;
        }
    }
}
